import datetime
import time
import MSFile
import os
import threading

def shouldArchive(e):
    #assume that events earlier than yesterday should be archived
    now = datetime.datetime.fromtimestamp(time.time())
    yesterday = datetime.datetime(now.year, now.month, now.day, 0,0,0,0) - datetime.timedelta(days=1)
    if e.eventGenerateTime < yesterday:
        return True
    return False


### generators ###
def genPhotos(events):
    for e in events:
        if e.photoContent:
            yield e
            continue
        if e.photoRef:
            yield e
            continue

def genMerged(ca, cb):
    a = iter(ca)
    b = iter(cb)
    x = a.next()
    y = b.next()
    while True:
        try:
            if x < y:
                yield x
                x = None
                x = a.next()
            if x > y:
                yield y
                y = None
                y = b.next()
            if x == y:
                yield x
                x = None
                y = None
                x = a.next()
                y = b.next()
        except StopIteration, e:
            break
    while True:
        try:
            if x: yield x
            x = a.next()
        except StopIteration, e:
            break
    while True:
        try:
            if y: yield y
            y = b.next()
        except StopIteration, e:
            break
###################

class MSObject:
    def __init__(self, path, objId):
        self.storage = MSFile.File(path, str(objId), genMerged, shouldArchive)

        self.objId  = objId
        self.events = []
        self.lock   = threading.Lock()

    '''
    def setMSID(self, msId):
        self.storage.meta['msId'] = msId

    def setPCNID(self, pcnId):
        self.storage.meta['pcnId'] = pcnId

    def setSKEY(self, skey):
        self.storage.meta['skey'] = skey
    '''

    def setOfflineCritical(self, enableOfflineCritical, offlineCriticalTimeout=600):
        self.storage.meta['enableOfflineCritical'] = enableOfflineCritical
        self.storage.meta['offlineCriticalTimeout'] = offlineCriticalTimeout

    def open(self, Locked=False):
        if not Locked: self.lock.acquire()
        try:
            self.storage.open()
        finally:
            if not Locked: self.lock.release()
    def load(self, Locked=False):
        if not Locked: self.lock.acquire()
        try:
            self.events = self.storage.load()
        finally:
            if not Locked: self.lock.release()
    def flush(self, Locked=False):
        if not Locked: self.lock.acquire()
        try:
            self.events = self.storage.save(self.events)
        finally:
            if not Locked: self.lock.release()
    def archive(self, Locked=False): 
        if not Locked: self.lock.acquire()
        try:
            self.events = self.storage.repack(self.events)
        finally:
            if not Locked: self.lock.release()

    def append(self, events, Locked=False):
        if not Locked: self.lock.acquire()
        try:
            if len(events) < 1: return
            events.sort()
            if len(self.events) < 1:
                self.events = events
                return
            t = []
            for e in genMerged(self.events, events):
                t.append(e)
            self.events = t
        finally:
            if not Locked: self.lock.release()


##########################
if __name__ == "__main__":
    import sys
    import MSEvent
    if len(sys.argv) != 2: print "Usage: %s <storage_root>" % (sys.argv[0])
    g_root = sys.argv[1]
    
    def case1():
        f1 = MSFile.File(g_root, 'test_obj')
        f1.destroy()

        o1 = MSObject(g_root, 'test_obj')
        o1.open()
        if o1.storage.txn != 0: raise RuntimeError('case1: failed 1')
        if o1.storage.meta != {}: raise RuntimeError('case1: failed 2')

        o1.storage.meta['test_param'] = 79
        o1.flush()

    def case2():
        o = MSObject(g_root, 'test_obj')
        o.open()
        if o.storage.txn != 1: raise RuntimeError('case2: failed 1')
        if o.storage.meta['test_param'] != 79: raise RuntimeError('case2: failed 2')

    def case3():
        o = MSObject(g_root, 'test_obj')
        o.open()
        o.storage.destroy()

        o = MSObject(g_root, 'test_obj')
        o.open()
        o.storage.meta['test_param'] = 79
        o.append([1, 2, 3])
        o.flush()

        o = MSObject(g_root, 'test_obj')
        o.open()
        o.load()
        if o.storage.txn != 1: raise RuntimeError('case3: failed 1')
        if o.storage.meta['test_param'] != 79: raise RuntimeError('case3: failed 2')
        if o.events != [1, 2, 3]: raise RuntimeError('case3: failed 3')

    def case4():
        o = MSObject(g_root, 'test_obj')
        o.open()
        o.storage.destroy()

        o = MSObject(g_root, 'test_obj')
        o.open()
        o.storage.meta['id'] = 18
        events = []
        for code in range(0, 5):
            e = MSEvent.Event()
            e.eventId  = code
            e.objectId = 18
            e.pgsmType = code
            events.append(e)
        o.append(events)
        o.flush()

        o = MSObject(g_root, 'test_obj')
        o.open()
        o.load()
        if len(o.events) != 5: raise RuntimeError('case4: failed 1')

        e = o.events[0]
        o.append([e])
        for e in o.events:
            print e.eventId, e.eventGenerateTime

        if len(o.events) != 5: raise RuntimeError('case4: failed 2')

    case1()
    case2()
    case3()
    case4()
